From: Filip Skokan Date: Mon, 25 May 2026 09:09:31 +0000 (+0200) Subject: crypto: guard WebCrypto cipher output length X-Git-Tag: archive/raspbian/18.20.4+dfsg-1_deb12u3+rpi1^2~8 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=61f8acff81607d37c6ae348892a166a093eec50c;p=nodejs.git crypto: guard WebCrypto cipher output length Reject WebCrypto cipher operations whose computed output length would exceed INT_MAX before passing the length to OpenSSL. This avoids signed overflow in the AES and ChaCha20-Poly1305 one-shot cipher paths and turns oversized inputs into a clean operation failure. Refs: https://hackerone.com/reports/3760016 Signed-off-by: Filip Skokan Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/879 Reviewed-By: Antoine du Hamel PR-URL: https://github.com/nodejs-private/node-private/pull/878 CVE-ID: CVE-2026-48933 origin: backport, https://github.com/nodejs/node/commit/38b4c5ed51b2ec81c28fbd379fea72e22fa12a15 bug: https://nodejs.org/en/blog/vulnerability/june-2026-security-releases#nodejs-webcrypto-aes-integer-overflow-leads-to-remote-process-abort-dos-cve-2026-48933---high Gbp-Pq: Topic sec Gbp-Pq: Name CVE-2026-48933.patch --- diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index c1c5bf762..e47ef3ef5 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -110,7 +110,15 @@ WebCryptoCipherStatus AES_Cipher( } size_t total = 0; - int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len; + const int block_size = EVP_CIPHER_CTX_block_size(ctx.get()); + if (block_size < 0) { + return WebCryptoCipherStatus::FAILED; + } + int buf_len; + if (!TryGetIntCipherOutputLength( + in.size(), static_cast(block_size) + tag_len, &buf_len)) { + return WebCryptoCipherStatus::FAILED; + } int out_len; if (mode == EVP_CIPH_GCM_MODE && @@ -146,7 +154,7 @@ WebCryptoCipherStatus AES_Cipher( total += out_len; CHECK_LE(out_len, buf_len); - out_len = EVP_CIPHER_CTX_block_size(ctx.get()); + out_len = block_size; if (!EVP_CipherFinal_ex( ctx.get(), buf.data() + total, &out_len)) { return WebCryptoCipherStatus::FAILED; diff --git a/src/crypto/crypto_cipher.h b/src/crypto/crypto_cipher.h index e725a2f30..98811090e 100644 --- a/src/crypto/crypto_cipher.h +++ b/src/crypto/crypto_cipher.h @@ -10,6 +10,7 @@ #include "memory_tracker.h" #include "v8.h" +#include #include namespace node { @@ -134,6 +135,18 @@ enum class WebCryptoCipherStatus { FAILED }; +inline bool TryGetIntCipherOutputLength(size_t input_len, + size_t output_overhead, + int* output_len) { + static constexpr size_t kMaxLength = INT_MAX; + if (output_overhead > kMaxLength || + input_len > kMaxLength - output_overhead) { + return false; + } + *output_len = static_cast(input_len + output_overhead); + return true; +} + // CipherJob is a base implementation class for implementations of // one-shot sync and async ciphers. It has been added primarily to // support the AES and RSA ciphers underlying the WebCrypt API. diff --git a/test/cctest/test_node_crypto.cc b/test/cctest/test_node_crypto.cc index 9d6405a40..00dd6304b 100644 --- a/test/cctest/test_node_crypto.cc +++ b/test/cctest/test_node_crypto.cc @@ -2,10 +2,13 @@ // and setting it to a file that does not exist. #define NODE_OPENSSL_SYSTEM_CERT_PATH "/missing/ca.pem" +#include "crypto/crypto_cipher.h" #include "crypto/crypto_context.h" +#include "gtest/gtest.h" #include "node_options.h" #include "openssl/err.h" -#include "gtest/gtest.h" + +#include /* * This test verifies that a call to NewRootCertDir with the build time @@ -21,3 +24,17 @@ TEST(NodeCrypto, NewRootCertStore) { "any errors on the OpenSSL error stack\n"; X509_STORE_free(store); } + +TEST(NodeCrypto, TryGetIntCipherOutputLength) { + int output_len = 0; + + EXPECT_TRUE( + node::crypto::TryGetIntCipherOutputLength(INT_MAX - 16, 16, &output_len)); + EXPECT_EQ(output_len, INT_MAX); + + EXPECT_FALSE( + node::crypto::TryGetIntCipherOutputLength(INT_MAX - 15, 16, &output_len)); + + EXPECT_FALSE(node::crypto::TryGetIntCipherOutputLength( + 0, static_cast(INT_MAX) + 1, &output_len)); +}